// 类型兼容性

// ts兼容性分成两种 子 extends 父   结构来考虑

let str: string = "abc"; // 类型层级
let obj!: { toString(): string };

obj = str; // 结构来考虑   extends object   extends {}

// 安全性 ts 主要考虑的就是安全， 安全就可以进行复制
// obj.toString

// 函数兼容性  （参数和返回值的兼容性）

let sum1 = (a: number, b: number): string | number => a + b;
let sum2 = (a: number): number => a;

type Sum1 = typeof sum1;
type Sum2 = typeof sum2;

type X = Sum2 extends Sum1 ? true : false;

// 对于函数而言他的兼容性， 少的可以赋予给多的， 参数少的是子类型
// 返回值要求安全, 返回值要求是子类型.
const forEach = <T>(
  arr: T[],
  callback: (val: T, key: number) => string | number
) => {
  for (let i = 0; i < arr.length; i++) {
    let r = callback(arr[i], i); // 调用函数的时候 会传递多个参数
  }
};
forEach(["A", 2, 3, {}], function (val) {
  return "abc";
});

// 类的兼容性 也是一样  比较的是实例
// class A {
//   private a = 1;
// }
// class B {
//   private a = 1;
// }
// const b: B = new A(); // 如果类中的属性 有private 或者protected则两个值不能互相复制

// ts 这种叫结构化类型，标称类型

// 希望给基本类型做区分，达到差异化的目的

type withType<T, K> = T & [K];
type BTC = withType<number, "BTC">;
type USDT = withType<number, "USDT">; // 基于内置类型来做构建

const c1 = 100 as BTC;
const c2 = 100 as USDT;
function money(val: BTC) {}
money(c1);

// 逆变（在函数参数可以标记儿子传父亲）和协变（可以标记父亲返回儿子）

class Parent {
  car() {}
}
class Child extends Parent {
  house() {}
}
class Grandson extends Child {
  sleep() {}
}
// 安全性考虑
// 1) 内部调用函数的时候 可以传递 Child 和 Grandson. 但是在使用属性时 只能认为最多就是child
// 2) 函数的返回值, 需要返回子类，因为内部代码在访问属性的时候要保证可以访问到
function fn(callback: (ctr: Child) => Child) {
  // 我交给回调的有房、有车
  let r = callback(new Child());
  r.house;
}
fn((child: Parent): Grandson => {
  return new Grandson();
});

// type Arg<T> = (arg: T) => void;
// type Return<T> = (arg: any) => T;
// type ArgReturn = Arg<Parent> extends Arg<Child> ? true : false; // 基于函数参数的逆变
// type ReturnReturn = Return<Grandson> extends Return<Child> ? true : false; // 返回值是协变的

// 逆变带来的问题（我们写业务的时候 还是要正常开启逆变）
interface MyArray<T> {
  //   concat: (...args: T[]) => T[];
  concat(...args: T[]): T[]; // 这种写法不进行逆变检测，所有在描述对象中的方法时全部采用这种方式
}
// parent: (...args: Parent[]) => Parent[];
// child:  (...args: Child[]) => Child[];
// 将child 赋予给parent   传父返儿子
let parentArr!: MyArray<Parent>;
let childArr!: MyArray<Child>;

// chilldArr 能不能赋予给 parentArr

// [{car(){}}] = [{car(){},house(){}}]

parentArr = childArr;
// childArr = parentArr;

// 其他的兼容性

enum E1 {
  a = 1,
}
enum E2 {
  a = 1,
}

let e1!: E1.a;
let e2!: E2.a;

// e2 = e1; //两个枚举之间 不能兼容

// 泛型兼容性， 如果生成的结果一致 类型就就兼容

type II<T> = { name?: T };

type X1 = II<string> extends II<string> ? true : false; // 生成结构一致即可

// 对象的兼容性， 多的属性可以赋予给少的
// 类型层级兼容性，never -> 字面量 -> 基础类型 -> 包装类型 -> any / unknown

// 子  extends  父  满足 即可赋值

// 类型推导的概念

// 1） 赋值推断 , 根据赋予的值来推断类型

let name = "jw";
let age = 30;

// 2) 函数时通过左边来推导右边, 基于上下文类型来进行自动的推导

// 3) 函数返回值编辑成void ，赋予一个函数的时候，意味着不关心返回值
// const sum: (a: string) => void = (a) => {
//   return a;
// };
// let r = sum("1");

function forEach1(arr: number[], callback: () => void) {
  // 这里的void 不关心返回值
  callback();
}
forEach1([1, 2, 3], function () {
  return [];
});

// 类型模板，类型保护，自己写类型
export {};

// 鸭子类型检测 结构化类型检测.。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
// 兼容分为两种
// 1，子类型可以赋予给父类型 ,
// 2，从结构角度出发。 ts比较的不是类型的名称，而是这个结构上的属性和方法

// 注意，将一个值赋予给另一个值可以产生兼容性...............................
// 1)基础类型的兼容性问题
let obj1: { tostring(): string };
let str1: string = "jw";
// 从安全的角度出发，你要的属性我都满足，只能访问已经存在的属性，不存在的无法方法
obj = str;

// 2)接口的兼容性  结构角度
interface IPerson {
  name: string;
  age: string;
}
interface IAnimal {
  name: string;
  age: string;
  address: string;
}
let person: IPerson = { name: "1", age: "2" };
let animal: IAnimal = { name: "1", age: "2", address: "3" };
// 在后台返回的数据中我们可以预先定义好接口类型。 多的属性也可以赋值给这个类型
person = animal;

//3)函数的兼容性
// 对于函数而言他的兼容性， 少的可以赋予给多的， 参数少的是父类型
// 返回值要求安全, 返回值要求是子类型，或者复杂结构数据

let s1 = (a: string, b: string) => a + b;
let s2 = (a: string) => a; // ts 基于位置来推导的
//要赋予的函数的参数个数只能少不能多。
s1 = s2;
function forEach11<T>(
  array: T[],
  callback: (item: T, index: number, array: T[]) => void
) {
  for (let index = 0; index < array.length; index++) {
    callback(array[index], index, array);
  }
}
forEach11([1, 2, 3], (item, index) => {});

// 逆变（在函数参数可以标记儿子传父亲）和协变（可以标记父亲返回儿子）...............................................
// 逆变（函数参数）和协变（函数返回值）

class Parent1 {
  car() {}
}
class Child1 extends Parent1 {
  house() {}
}
class Grandson1 extends Child1 {
  money() {}
}
// 安全性考虑
// 1) 内部调用函数的时候 可以传递 Child 和 Grandson. 但是在使用属性时 只能认为最多就是child
// 2) 函数的返回值, 需要返回子类，因为内部代码在访问属性的时候要保证可以访问到
function fn1(callback: (instance: Child1) => Child1) {
  // 我交给回调的有房、有车
  let child = new Child1();
  let ins: Child1 = callback(child);
  //这个就是协变 ，为什么函数返回值 可以写Grandson，但是不能写 Parent
  // 回调函数返回值的是Child1的实例，如果回调函数返回值 的类型是Parent，那么Parent1的实例是不可以访问到Child1独有的属性
  // 如果回调函数参数的类型是Grandson，那么Grandson的实例是绝对可以访问到Child1规定的属性
  // 回调函数返回值Parent，但可能会用到Grandson独有的属性。导致传ParentChild1实列不安全
  return ins;
}
//这个就是逆变 ，为什么赋予的函数参数instance数据类型时 可以写Parent但是不能写Grandson，
// 回调函数传入的是Child1的实例，如果回调函数参数的类型是Parent，那么Child1的实例是绝对可以访问到Parent规定的属性
// 如果回调函数参数的类型是Grandson，那么Child1的实例是不可以访问到Grandson独有的属性
// 回调函数内部，可能会用到Grandson独有的属性。导致传Child1实列不安全
fn1((instance: Child1) => {
  return instance;
});

//let t1:(instance:Child)=>void =(instance:Parent)=>"";// 函数的参数是逆变的//let t2:(instance:Child)=>child=(instance:child)=>new Grandson();// 函数的返回值是协变内//传递的函数(传父(参数是逆变的)返子(返回值是协变的))
//对于函数的兼容性而言，参数个数要少，传递的可以是父类，返回值可以返回儿子
// 推导公式:
type Arg<T> = (arg: T) => void;
type Return<T> = (arg: any) => T;
type ArgType = Arg<Parent1> extends Arg<Child1> ? true : false; // 逆变
type ReturnType = Return<Grandson1> extends Return<Child1> ? true : false; // 协变
// 避免协变的方法
// 方案一
interface MyArray<T> {
  concat(...args: T[]): T[]; // 不会对参数进行逆变检测, 写类的方法常用它
  // concat: (...args: T[]) => T[]; // 这种方式会检测逆变
}
let arr1!: MyArray<Parent>;
let arr2!: MyArray<Child>;
//arr1->(...args: Parent[]): Parent[];
//arr2->(...args: child[]): Child[];
arr1 = arr2;
// 方案二
// strictFunctionTypes 开启后就变成了双向协变，参数和返回值都是协变的
//    // "strictFunctionTypes": true,

// 泛型的兼容性 结构一致即可   ts 比较的是结构，结构一致即可..................................................................................
interface TT<T> {}
let o1!: TT<string>;
let o2!: TT<number>;
o1 = o2;
// 泛型的兼容性 枚举不具备兼容性问题............................................................................
enum E1 {}
enum E2 {}
let e11!: E1;
let e22!: E2;
// e2 =e1;

// 类的兼容性 // 比较的是属性， 不符合就不兼容。如果类中存在私有属性或者受保护的属性，则不能兼容
//ts 比较类型结构的时候比较的是属性和方法//如果属性和方法都满足则兼容，有一些比较特殊

/*

// 可以完全相互赋值的情况
class A {
  public name!: string;
}
class B {
  public name!: string;
}
// let b: A = new B();
// let b: B = new A();

*/
/* 
// 可以完全相互赋值的情况
class A {
  public name!: string;
}
class B {
  public name!: string;
  public age!: string;
}
// let b: A = new B(); // 可以兼容
// let b: B = new A(); // 不可以兼容
*/
/* 
// 如果类中存在私有属性或者受保护的属性，则不能兼容
class A {
  private name!: string;
}
class B {
  private name!: string;
  public age!: string;
}
// let b: A = new B();
*/

//基础类型和对象类型的兼容,接口的兼容， 泛型的兼容，枚举的兼容，类的兼容

//在其他语言中存在标称类型(根据名称来区分类型)通过交又类型实现标称类型.。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
type Nominal<T, K extends string> = T & { _tag: K };
type BTC1 = Nominal<number, "btc">;
type USDT1 = Nominal<number, "usdt">;
let btc1: BTC1 = 1000 as BTC1;
let usdt1: USDT1 = 1000 as USDT1;
function getVal1(val: BTC1) {
  return val;
}
getVal1(btc1);
